Option Explicit

' 将 markdown 中的表格数据转换为二维数组
' 表头和第二行的 `|--|--|` 会正常分割并被保留
'
' @param mdTableTxt 需要转换的 markdown 表格字符串数据
' @optional-param skipSecondSplitRow 是否需要忽略 markdown 表格第二行的表头和内容间的分割行
'                                    默认值 = True，将会忽略这一行
' @return 转换后的二维字符串数组
Public Function transMdTableTo2DArray(ByRef mdTableTxt As String, Optional ByRef skipSecondSplitRow As Boolean = True) As Variant
    Dim result () As Variant

    ' 1. 去除末尾的换行符
    Dim tmp As String
    tmp = UtilsString.deleteStrEndLineBreaks(mdTableTxt)

    ' 2. 分割表格字符串
    Dim mdTableRowArr() As String
    mdTableRowArr = UtilsString.splitByAnyLineBreak(tmp)

    ' 3. 获取表格的行数
    Dim maxIndex As Integer
    maxIndex = UBound(mdTableRowArr)

    ' 4. 如果是空参数，则返回 (0, 0) 长度的数组
    If maxIndex = -1 Then
        transMdTableTo2DArray = Array()
        Exit Function
    End If

    ' 5. 检查是否需要跳过第二行
    Dim needSkipSecondRow As Boolean
    needSkipSecondRow = False
    If skipSecondSplitRow Then
        ' 检查是否存在第二行
        If maxIndex >= 1 Then
            ' 检查第二行是否是分割行
            tmp = UtilsString.removeAllByReg(mdTableRowArr(1), "[ |:]")
            ' 如果第二行全部是 -，则是分割行
            If UtilsString.isAllSameSpecialChar(tmp, "-") Then
                ' 只有到达这里才能确认是分割行，其他情况全部不是！！！！！
                needSkipSecondRow = True
            End If
        End If
    End If

    ' 5. 遍历并生成结果
    Dim i As Integer
    If needSkipSecondRow Then
        ' 处理第一行
        ReDim result (0 To maxIndex - 1)
        result(0) = transMdTableRowToArray(mdTableRowArr(0))

        ' 处理第三行之后的所有行
        Dim resultIndex As Integer
        resultIndex = 1
        For i = 2 To maxIndex
            result(resultIndex) = transMdTableRowToArray(mdTableRowArr(i))
            resultIndex = resultIndex + 1
        Next
    Else
        ReDim result (0 To maxIndex)
        For i = 0 To maxIndex
            result(i) = transMdTableRowToArray(mdTableRowArr(i))
        Next
    End If

    transMdTableTo2DArray = result
End Function

' 将 mdTable 中的一行数据使用 `|` 分割为一个数组
'
' @param mdTableRowTxt markdown表格中某一行的内容
' @return markdown表格中某一行按照 | 分割后的字符串数组
Private Function transMdTableRowToArray(ByRef mdTableRowTxt As String) As Variant
    Dim result() As String
    Dim tmp As String

    ' 1. 去除行字符串两侧的空格
    tmp = Trim(mdTableRowTxt)

    ' 2. 去除行字符串两侧的 `|`
    tmp = UtilsString.trimBy(tmp, "|")
    tmp = Trim(tmp)

    ' 3. 使用 `|` 分割字符串
    Dim mdTableCellArray() As String
    mdTableCellArray = Split(tmp, "|")

    ' 4. 获取分割获得元素个数
    Dim maxIndex As Integer
    maxIndex = UBound(mdTableCellArray)

    ' 5. 如果分割后没有元素，则返回空数组
    If maxIndex = -1 Then
        transMdTableRowToArray = Array()
        Exit Function
    End If

    ' 6. 生成数组
    ReDim result (0 To maxIndex)
    Dim i As Integer
    For i = 0 To maxIndex
        result(i) = Trim(mdTableCellArray(i))
    Next
    transMdTableRowToArray = result
End Function

' 将 markdown 的表格转换为二维数组，然后写入指定的 sheet，并给写入区域设置边框
'
' @param mdTableTxt 需要转换的 markdown 表格字符串数据
' @param sh 需要写入转换后数据的 sheet 对象
' @optional-param startCellName 输出到 sheet 时的起始单元格
'                               默认值 = `B2`，从第二行的第二列开始写入
' @optional-param needEmptyRow 遇到空行时，是否需要在 sheet 的输出区域中保留那一行
'                              默认值 = `True`，保留那一行
' @optional-param skipSecondSplitRow 是否需要忽略 markdown 表格第二行的表头和内容间的分割行
'                                    默认值 = True，将会忽略这一行
Public Sub transMdTableToSheet(ByRef mdTableTxt As String, ByRef sh As Worksheet, _
    Optional ByRef startCellName As String = "B2", _
    Optional ByRef needEmptyRow As Boolean = True, _
    Optional ByRef skipSecondSplitRow As Boolean = True _
)
    ' 1. 将 markdown 表格字符串转换为二维数组
    Dim rowArr As Variant
    rowArr = transMdTableTo2DArray(mdTableTxt, skipSecondSplitRow := skipSecondSplitRow)

    '   如果是空数组，则立刻结束
    If UBound(rowArr) = -1 Then
        Exit Sub
    End If

    ' 2. 写入数据
    writeMdTableArrayToSheet rowArr, sh, startCellName, needEmptyRow
End Sub

' 将 markdown 表格转换后的二维数组写入指定的 sheet 对象中
'
' @param mdTable2DArr markdown 表格转换后的二维数组
' @param sh 需要写入转换后数据的 sheet 对象
' @optional-param startCellName 输出到 sheet 时的起始单元格
'                               默认值 = `B2`，从第二行的第二列开始写入
' @optional-param needEmptyRow 遇到空行时，是否需要在 sheet 的输出区域中保留那一行
'                              默认值 = `True`，保留那一行
Public Sub writeMdTableArrayToSheet( _
    ByRef mdTable2DArr As Variant, _
    ByRef sh As Worksheet, _
    Optional ByRef startCellName As String = "B2", _
    Optional ByRef needEmptyRow As Boolean = True _
)
    Dim i As Integer
    Dim j As Integer
    Dim maxCellIndex As Integer
    Dim cellEndIndex As Integer
    Dim writeIndex As Integer
    Dim cellArr As Variant

    Dim maxRowIndex As Integer
    maxRowIndex = UBound(mdTable2DArr)

    With sh
        For i = 0 To maxRowIndex
            cellArr = mdTable2DArr(i)
            cellEndIndex = UBound(cellArr)

            ' 如果是空行，按照 needEmptyRow 来处理空行
            If cellEndIndex = -1 Then
                If needEmptyRow Then
                    writeIndex = writeIndex + 1
                End If
                GoTo NextTableRow
            End If

            .Range(startCellName).Offset(writeIndex, 0).Resize(1, cellEndIndex + 1) = cellArr

            If cellEndIndex > maxCellIndex Then
                maxCellIndex = cellEndIndex
            End If

            writeIndex = writeIndex + 1
NextTableRow:
        Next
        ' 如果所有行都是空行，则立即结束，不需要设置边框
        ' 如字符串:
        '   "| |"
        '   "| |"
        '   "| |"
        If writeIndex = 0 Then
            Exit Sub
        End If

        ' 3. 设置单元格的边框
        UtilsWorksheet.setRangeAllDefaultBorders .Range(startCellName).Resize(writeIndex, maxCellIndex + 1)
    End With

End Sub

' 将 markdown 的表格转换为二维数组，然后写入当前的活动 sheet，并给写入区域设置边框
'
' @param mdTableTxt 需要转换的 markdown 表格字符串数据
' @optional-param startCellName 输出到 sheet 时的起始单元格
'                               默认值 = `B2`，从第二行的第二列开始写入
' @optional-param needEmptyRow 遇到空行时，是否需要在 sheet 的输出区域中保留那一行
'                              默认值 = `True`，保留那一行
' @optional-param skipSecondSplitRow 是否需要忽略 markdown 表格第二行的表头和内容间的分割行
'                                    默认值 = True，将会忽略这一行
Public Sub transMdTableToActiveSheet(ByRef mdTableTxt As String, _
    Optional ByRef startCellName As String = "B2", _
    Optional ByRef needEmptyRow As Boolean = True, _
    Optional ByRef skipSecondSplitRow As Boolean = True _
)
    transMdTableToSheet mdTableTxt, _
                        ActiveSheet, _
                        startCellName, _
                        needEmptyRow, _
                        skipSecondSplitRow
End Sub

' 将 markdown 的表格转换为二维数组并创建一个新 sheet 写入
'
' @param mdTableTxt 需要转换的 markdown 表格字符串数据
' @optional-param wb 需要创建新 sheet 的 Workbook 对象
'                    默认值 = ActiveWorkBook，当前的活动 Workbook 对象
' @optional-param newSheetName 需要创建新 sheet 的名称
'                    默认值 = "mdTable"
' @optional-param startCellName 输出到 sheet 时的起始单元格
'                               默认值 = `B2`，从第二行的第二列开始写入
' @optional-param needEmptyRow 遇到空行时，是否需要在 sheet 的输出区域中保留那一行
'                              默认值 = `True`，保留那一行
' @optional-param skipSecondSplitRow 是否需要忽略 markdown 表格第二行的表头和内容间的分割行
'                                    默认值 = True，将会忽略这一行
' @return 新创建的 sheet 名
Public Function transMdTableToNewSheet(ByRef mdTableTxt As String, _
    Optional ByRef wb As WorkBook = Nothing, _
    Optional ByRef newSheetName As String = "mdTable", _
    Optional ByRef startCellName As String = "B2", _
    Optional ByRef needEmptyRow As Boolean = True, _
    Optional ByRef skipSecondSplitRow As Boolean = True _
) As String
    If wb Is Nothing Then Set wb = ActiveWorkbook

    ' 1. 将 markdown 表格字符串转换为二维数组
    Dim rowArr As Variant
    rowArr = transMdTableTo2DArray(mdTableTxt, skipSecondSplitRow := skipSecondSplitRow)

    '   如果是空数组，则立刻结束
    If UBound(rowArr) = -1 Then
        Exit Function
    End If

    ' 2. 创建新 sheet
    ' 2.1 获取可用的 sheet 名
    Dim usableSheetName As String
    If newSheetName = "" Then
        usableSheetName = "mdTable"
    Else
        usableSheetName = newSheetName
    End If
    usableSheetName = UtilsWorksheet.getUsableSheetName(wb, usableSheetName)

    ' 2.2 在所有 sheet 的末尾创建新 sheet
    Dim sh As Worksheet
    Set sh = wb.Sheets.Add(after:=wb.Sheets(wb.Sheets.Count))
    sh.Name = usableSheetName

    ' 3. 写入数据
    writeMdTableArrayToSheet rowArr, sh, startCellName, needEmptyRow

    transMdTableToNewSheet = usableSheetName
End Function

' ==================================================
' 使用二维数组生成 markdown table
' ==================================================
' 将 2 维数组数据转换为一个 markdown 表格字符串数据
'
' @param data 用来生成 markdown 表格的二维数组
' @optional-param secondIsSplitRow 数据的第二行是否是分割行
'                 默认值 = False，表示第二行不是分割行，是输入数据
'                 如果该参数为 `True`，则无论 data 的第二行是什么都会直接忽略
' @optional-param needEmptyRow 是否需要保留空行数据
'                默认值 = `True`，保留空行
'                如果设置为 `True` 保留空行时，将会以第一行（表头）的列数为基准生成对应的空行字符串
' @optional-param lineBreak 换行符
'                 默认值 = `vbCrLf`
' @return markdown 表格字符串
Public Function trans2DArrToMdTableTxt( _
    ByRef data As Variant, _
    Optional ByRef secondIsSplitRow As Boolean = False, _
    Optional ByRef needEmptyRow As Boolean = False, _
    Optional ByRef lineBreak As String = vbCrLf _
) As String
    ' 1. 获取 1 维边界值
    Dim max1DIndex As Integer
    Dim min1DIndex As Integer
    max1DIndex = UBound(data)
    min1DIndex = LBound(data)

    If min1DIndex > max1DIndex Then
        Exit Function
    End If

    ' 2. 生成表头
    Dim i As Integer
    Dim j As Integer
    Dim rowData As Variant
    Dim max2DIndex As Integer
    Dim min2DIndex As Integer
    Dim titleColCount As Integer

    i = min1DIndex
    rowData = data(i)
    i = i + 1
    min2DIndex = LBound(rowData)
    max2DIndex = UBound(rowData)
    titleColCount = max2DIndex - min2DIndex + 1

    trans2DArrToMdTableTxt = joinMdTableRowStr(rowData, min2DIndex, max2DIndex)

    ' 3. 生成分割行
    If secondIsSplitRow Then
        rowData = data(i)
        i = i + 1

        ' 3.1 如果第二行是分割行，则按照行内的内容生成分割行
        min2DIndex = LBound(rowData)
        max2DIndex = UBound(rowData)

        If min2DIndex > max2DIndex Then
            ' 如果是空行，则按照表头数生成分割行
            trans2DArrToMdTableTxt = _
                    trans2DArrToMdTableTxt & _
                    lineBreak & _
                    joinMdTableSplitRowStr(titleColCount)
        Else
            ' 按照这一行的内容生成
            trans2DArrToMdTableTxt = _
                    trans2DArrToMdTableTxt & _
                    lineBreak & _
                    joinMdTableRowStr(rowData, min2DIndex, max2DIndex)
        End If
    Else
        ' 3.3 如果第二行不是分割行，则按照表头的列数生成分割行
        trans2DArrToMdTableTxt = _
                trans2DArrToMdTableTxt & _
                lineBreak & _
                joinMdTableSplitRowStr(titleColCount)
    End If

    ' 4. 遍历生成数据
    For i = i To max1DIndex
        rowData = data(i)
        max2DIndex = UBound(rowData)
        min2DIndex = LBound(rowData)

        ' 如果是空行
        If min2DIndex > max2DIndex Then
            ' 只在需要空行时，才则按照表头数生成空行字符串
            If needEmptyRow Then
                trans2DArrToMdTableTxt = _
                        trans2DArrToMdTableTxt & _
                        lineBreak & _
                        joinMdTableEmptyRowStr(titleColCount)
            End If
        Else
            trans2DArrToMdTableTxt = _
                    trans2DArrToMdTableTxt & _
                    lineBreak & _
                    joinMdTableRowStr(rowData, min2DIndex, max2DIndex)
        End If
    Next

End Function

' 根据一行数据生成 markdown table 的一行字符串字符串
Private Function joinMdTableRowStr( _
    ByRef rowData As Variant, _
    ByRef minIndex As Integer, _
    ByRef maxIndex As Integer _
) As String
    Dim j As Integer
    Dim s As String

    joinMdTableRowStr = joinMdTableRowStr & "|"
    For j = minIndex To maxIndex
        s = rowData(j)
        joinMdTableRowStr = joinMdTableRowStr & _
            " " & _
            UtilsString.escapeTargetStr(s, "|") & _
            " |"
    Next
End Function

' 创建 markdown table 的空行字符串
Private Function joinMdTableEmptyRowStr(ByRef colCount As Integer) As String
    Dim j As Integer

    joinMdTableEmptyRowStr = joinMdTableEmptyRowStr & "|"
    For j = 0 To colCount - 1
        joinMdTableEmptyRowStr = joinMdTableEmptyRowStr & _
            " |"
    Next
End Function

' 创建 markdown table 的分割行字符串
Private Function joinMdTableSplitRowStr(ByRef colCount As Integer) As String
    Dim j As Integer

    joinMdTableSplitRowStr = joinMdTableSplitRowStr & "|"
    For j = 0 To colCount - 1
        joinMdTableSplitRowStr = joinMdTableSplitRowStr & _
            " - |"
    Next
End Function

' ==================================================
' 使用 Range 数据生成 markdown table
' ==================================================
' 读取指定区域中的数据，并转换为 markdown 表格字符串
'
' @param rng 区域对象
' @optional-param secondIsSplitRow 数据的第二行是否是分割行
'                       默认值 = `False`，表示第二行不是分割行，是输入数据
'                       如果该参数为 `True`，则无论 data 的第二行是什么都会直接忽略
' @optional-param lineBreak 换行符
'                       默认值 = `vbCrLf`
' @return markdown 表格字符串
Public Function transRangeToMdTableTxt( _
    ByRef rng As Range, _
    Optional ByRef secondIsSplitRow As Boolean = False, _
    Optional ByRef lineBreak As String = vbCrLf _
) As String
    ' 1. 获取 range 的相关数据
    Dim colCount As Integer
    colCount = rng.Columns.Count
    Dim rowCount As Integer
    rowCount = rng.Rows.Count

    ' 2. 生成表头
    Dim i As Integer
    i = 1
    
    transRangeToMdTableTxt = joinMdTableRowStrByRange(rng.Rows(i))
    i = i + 1

    ' 3. 生成分割行
    If secondIsSplitRow Then
        ' 3.1 如果第二行时分割行，则按照内的内容生成分割行
        transRangeToMdTableTxt = _
                transRangeToMdTableTxt & _
                lineBreak & _
                joinMdTableRowStrByRange(rng.Rows(i))
        i = i + 1
    Else
        ' 3.2 如果第二行不是分割行，则按照表头的列数生成分割行
        transRangeToMdTableTxt = _
                transRangeToMdTableTxt & _
                lineBreak & _
                joinMdTableSplitRowStr(colCount)
    End If

    ' 4. 遍历生成数据
    For i = i To rowCount
        transRangeToMdTableTxt = _
                transRangeToMdTableTxt & _
                lineBreak & _
                joinMdTableRowStrByRange(rng.Rows(i))
    Next
End Function

' 根据一行数据生成 markdown table 的一行字符串字符串
Private Function joinMdTableRowStrByRange(ByRef rng As Range) As String
    Dim s As String

    joinMdTableRowStrByRange = joinMdTableRowStrByRange & "|"
    Dim cell As Range
    For Each cell In rng.Cells
        s = cell.value
        joinMdTableRowStrByRange = joinMdTableRowStrByRange & _
            " " & _
            UtilsString.escapeTargetStr(s, "|") & _
            " |"
    Next cell
End Function